﻿using System;
using System.Data;
using System.Data.Common;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.ComponentModel;

using RAD.LightOne.Data;
using RAD.LightOne;
using RAD.LightOne.Cache;
using RAD.LightOne.ComponentModel.Aspects;

namespace RAD.LightOne
{

    #region Documentation
    /// <summary>
    /// Classe abstrata que tem como finalidade ser a base de classes que se utilizem das funcionalidades
    /// para persistência oferecidas pelo LightOne.
    /// A construção de novos objetos do LightOne baseados neste MyObject não tipado é desencorajada mas permitida.
    /// </summary>
    #endregion Documentation
    [Serializable]
    [Id]
    [ClassName]
    [LastPersistence]
    public abstract partial class MyObject : MarshalByRefObject,  
        IComparable, ICloneable, IMyObject, IEquatable<MyObject>, INotifyPropertyChanged
    {
        #region Events
        #region Documentation
        /// <summary>
        /// Evento disparado ao início da operação de persistência.
        /// </summary>
        #endregion Documentation
        public event MyObjectTransactionHandler Saving;
        #region Documentation
        /// <summary>
        /// Evento disparado ao fim da operação de persistência.
        /// </summary>
        #endregion Documentation
        public event MyObjectTransactionHandler Saved;
        #region Documentation
        /// <summary>
        /// Evento disparado ao início da operação de carregamento.
        /// </summary>
        #endregion Documentation
        public event MyObjectTransactionHandler Loading;
        #region Documentation
        /// <summary>
        /// Evento disparado ao fim da operação de carregamento.
        /// </summary>
        #endregion Documentation
        public event MyObjectTransactionHandler Loaded;
        #region Documentation
        /// <summary>
        /// Evento disparado ao início da operação de exclusão.
        /// </summary>
        #endregion Documentation
        public event MyObjectTransactionHandler Deleting;
        #region Documentation
        /// <summary>
        /// Evento disparado ao fim da operação de exclusão.
        /// </summary>
        #endregion Documentation
        public event MyObjectTransactionHandler Deleted;

        #endregion Events

        #region Fields

        #region Documentation
        /// <summary>
        /// armazenamento base para o ID, em forma de string para facilitar futuras alterações.
        /// </summary>
        #endregion
        protected string id;

        //[DBField("class_name")]
        protected string className;

        //[DBField("last_persistence")]
        protected DateTime lastPersistence;

        #endregion

        #region Properties

        #region Documentation
        /// <summary>
        /// Retorna o id único da instância persistida.
        /// </summary>
        #endregion Documentation
        public virtual Guid Id
        {
            get
            {
                try
                {
                    if(string.IsNullOrEmpty(this.id))
                    {
                        return Guid.Empty;
                    }
                    else
                    {
                        return new Guid(this.id);
                    }
                }
                catch
                {
                    return Guid.Empty;
                }
            }
            set
            {
                //if (value == Guid.Empty || value == null) throw new ArgumentNullException("MyObject constructor, id argument cannot be null.");
                if (value == null) throw new ArgumentNullException("MyObject constructor, id argument cannot be null.");

                this.id = value.ToString();
            }
        }

        #region Documentation
        /// <summary>
        /// Retona o o FullName do real tipo da classe.
        /// </summary>
        #endregion Documentation
        public string ClassName
        {
            get
            {
                return this.className;
            }
        }

        #region Documentation
        /// <summary>
        /// Retorna a data da última operação de persistência no objeto.
        /// Sua representação deve aparecer uma única vez nas tabelas envolvidas em uma hierarquia de tipos,
        /// diferentemente de Id e ClassName que deve estar presentes em todas as tabelas de uma hierarquia.
        /// </summary>
        #endregion Documentation
        public virtual DateTime LastPersistence
        {
            get
            {
                return this.lastPersistence;
            }
            set 
            {
                this.lastPersistence = value; 
            }
        }

        #endregion

        #region Constructors
        #region Documentation
        /// <summary>
        /// Construtor necessário, todas as classes específicas que herdem de MyObject devem obrigatóriamente possuir um 
        /// contrutor não público que endereçe este.
        /// </summary>
        /// <param name="id">id utilizado para localizar e recarregar a instância.</param>
        #endregion
        [Obsolete("Before 1.0.2.* version this constructor is no more required in inherited classes", false)]
        protected MyObject(Guid id) : base()
        {
            if (id == Guid.Empty || id == null) throw new ArgumentNullException("MyObject constructor, id argument cannot be null.");

            Initialize(id);
        }

        #region Documentation
        /// <summary>
        /// Construtor padrão para crição de novas instâncias.
        /// </summary>
        #endregion
        protected MyObject() : base()
        {
            Initialize(Guid.Empty);
        }

        #endregion

        #region Methods

        #region Aux Methods

        private static object VerifyQueryLocker = new object();

        #region Documentation
        /// <summary>
        /// Verifica a existênca de campos requeridos nas sql's caso não encontre, dispara exception
        /// </summary>
        /// <param name="query">string com comando sql "select" a ser analisada.</param>
        #endregion Documentation
        [Obsolete("não utilize, invalido utilizando-se campos chave, id, class name, last... cusomizáveis.",true)]
        internal protected static void VerifyQuery(string query)
        {
            lock (VerifyQueryLocker)
            {
                if (!query.ToLower().Contains("id") || !query.ToLower().Contains("class_name"))
                    throw new System.Data.SyntaxErrorException("Required fields not present on query, id and class_name.");
            }
        }

        private static object VerifyIsIMyObjectLocker = new object();

        #region Documentation
        /// <summary>
        /// Verifica se um tipo passado implementa IMyObject.
        /// </summary>
        /// <param name="type">tipo analizado.</param>
        #endregion
        internal protected static void VerifyIsIMyObject(Type type)
        {
            lock (VerifyIsIMyObjectLocker)
            {
                if (type.GetInterface(typeof(IMyObject).FullName) == null)
                {
                    throw new ArgumentException("The type passed don't be or don't implements IMyObject.");
                }
            }
        }

        private static object ImplicitlyIsLocer = new object();
        #region Documentation
        /// <summary>
        /// Avalia se a instância é implicitamente do tipo passado, ou seja, é ou herda de tal tipo.
        /// </summary>
        /// <param name="type">tipo a ser comparado com o tipo da instância.</param>
        #endregion
        public virtual bool ImplicitlyIs(Type type)
        {
            lock (ImplicitlyIsLocer)
            {
                return (this.GetType().IsSubclassOf(type) || this.GetType() == type);
            }
        }

        private static object IsMyObjectLocker = new object();
        #region Documentation
        /// <summary>
        /// Verifica se um tipo passado é ou herda de MyObject.
        /// </summary>
        /// <param name="type">tipo a ser analizado</param>
        #endregion
        public static bool IsMyObject(Type type)
        {
            lock (IsMyObjectLocker)
            {
                return (type.IsSubclassOf(typeof(MyObject)) || type == typeof(MyObject));
            }
        }

        #region Documentation
        /// <summary>
        /// Atualiza a data da última persistência para a data atual. Chamado dentro dos métodos de persistência de DBengine
        /// para controle de concorrência otimista.
        /// </summary>
        #endregion Documentation
        internal void RefreshLastPersistence()
        {
            this.lastPersistence = DateTime.Now;
        }

        #endregion

        private void Initialize(Guid id)
        {
            this.className = this.GetType().FullName;
            this.id = id.ToString();
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        public override bool Equals(object obj)
        {
            if (obj is MyObject)
            {

                MyObject other = obj as MyObject;

                if (this.Id == Guid.Empty || other.Id == Guid.Empty)
                {

                    return object.ReferenceEquals(this, obj);

                }
                else
                {

                    return this.id.Equals(other.id);

                }

            }
            else
            {
                return base.Equals(obj);
            }
        }

        #endregion Methods

        #region IComparable Members

        #region Documentation
        /// <summary>
        /// Avalia, pelo Id exclusivamente, se as instâncias são iguais.
        /// </summary>
        /// <param name="obj">Instância a ser comparada com esta.</param>
        /// <returns>0 iguais, -1 instâncias diferentes, -2 tipos diferentes.</returns>
        #endregion
        public int CompareTo(object obj)
        {
            if (obj is MyObject)
            {
                if (this.Id == ((MyObject)obj).Id) return 0;
                return -1;
            }
            else
            {
                return -2;
            }
        }

        #endregion

        #region ICloneable Members

        #region Documentation
        /// <summary>
        /// Retorna uma cópia sem Id do objeto atual, apenas dados pertinentes.
        /// </summary>
        #endregion
        public object Clone()
        {
            MyObject newObject = this.MemberwiseClone() as MyObject;
            newObject.id = string.Empty;
            return newObject;
        }

        #region Documentation
        /// <summary>
        /// Retorna um clone do objeto atual inclusive com ID, útil na utilização do cache
        /// de objetos.
        /// </summary>
        /// <param name="id">Id da instância.</param>
        #endregion Documentation
        internal MyObject Clone(Guid id)
        {
            MyObject newObject = this.Clone() as MyObject;
            newObject.id = id.ToString();
            return newObject;
        }

        #endregion

        #region Operators

        public static bool operator ==(MyObject A, MyObject B)
        {

            if ((object)A == null || (object)B == null)
            {
                return (object)A == (object)B;
            }
            else
            {
                return A.Equals(B);
            }

        }

        public static bool operator !=(MyObject A, MyObject B)
        {

            return !(A == B);

        }

        #endregion

        #region IEquatable<MyObject> Members

        public bool Equals(MyObject other)
        {
            if (other == null) return false;

            return other.id == this.id;
        }

        #endregion

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #region Documentation
        ///<summary>
        /// Método de suporte. Criado para servir de disparador para o evento PropertyChanged definido na interface <see cref="INotifyPropertyChanged"/>, PropertyChanged;
        ///</summary>
        #endregion
        internal void CallPropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion
    }
}
